Setup

Packages

First we load up the packages we’ll be working with

# remotes::install_github("mathesong/bloodstream")
# remotes::install_github("mathesong/kinfitr")

library(tidyverse)
library(bloodstream)
library(kinfitr)
library(job)

theme_set(theme_light())

Data

Here is an optional chunk to download the minified dataset. However, it should already be included in the binder.

download.file("https://www.dropbox.com/scl/fi/ax1t3lfop3pp3h4cir0m7/ds004869.zip?rlkey=6z7e9wopud9ngu26ekbd3kfks&dl=1", 
              destfile = "ds004869.zip")
unzip(zipfile = "ds004869.zip")

Blood Analysis

Here we can run a basic blood analysis. For anyone who wants to run it, I’ve placed it in a job call, which runs it in the background, but it may or may not make your session slow.

job({ 
  bloodstream("ds004869/") 
})

Alternatively, if you want to use a configuration file, this is how you could do so. You can inspect the config file in the relevant directory. But best not to run this: this takes a little longer, and it is not in a job call either, so you won’t be able to do anything until it is completed.

bloodstream("ds004869/", configpath = "ds004869/code/config_tutorial.json") 

Loading Data

Loading bloodstream outputs

We want the arterial input function data, which are called “input”.

bloodstream_data <- bloodstream_import_inputfunctions("ds004869/derivatives/bloodstreamtutorial/") %>%
  select(-measurement)

Let’s plot a few of them to see. These contain the interpolated curves, and can be used with kinfitr to perform kinetic modelling.

head(bloodstream_data, n = 6)

bloodstream_data %>% 
  slice(1:6) %>% 
  pull(input) %>% 
  map(plot)
[[1]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_line()`).

[[2]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_line()`).

[[3]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_line()`).

[[4]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_line()`).

[[5]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_line()`).

[[6]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_line()`).

Loading petprep outputs (TACs and morphology)

Now we want to load up the petprep outputs. There will eventually be a function for automating the next few steps, but the PET BIDS Derivatives guidelines are not yet nailed down, so we’ll do it manually for now.

First we parse the files and load in the data for the tacs and the morphology.

petprep_data <- kinfitr::bids_parse_files("ds004869/derivatives/petprep_extract_tacs/") %>% 
  unnest(filedata) %>% 
  filter(str_detect(path_absolute, "gtmseg"))

tacs <- petprep_data %>% 
  filter(measurement=="tacs") %>% 
  filter(is.na(pvc)) %>% 
  mutate(tacdata = map(path_absolute, ~read_delim(.x, delim="\t", 
                                                  show_col_types = FALSE)))

morphdata <- petprep_data %>%
  filter(measurement=="morph") %>%
  mutate(morphdata = map(path_absolute, ~read_delim(.x, delim="\t",
                                                  show_col_types = FALSE)))

Now we have lots of little regions of interest, but they are all very small. Typically, we would want to combine regions into larger regions that we are interested in. We do so using volume-weighted averaging, which requires both the morphology and the TAC information. This will also soon be automated once the PET BIDS Derivatives are more finalised.

Creating combined TACs

We’ll create a frontal cortex region, a striatal region and a hippocampus-amygdala region.

First, let’s define the regions by their constitutent parts.

frontal_regions <- morphdata$morphdata[[1]] %>% 
  filter(str_detect(name, "frontal")) %>% 
  pull(name)

frontal_regions
 [1] "ctx-lh-caudalmiddlefrontal"  "ctx-lh-lateralorbitofrontal" "ctx-lh-medialorbitofrontal"  "ctx-lh-rostralmiddlefrontal"
 [5] "ctx-lh-superiorfrontal"      "ctx-lh-frontalpole"          "ctx-rh-caudalmiddlefrontal"  "ctx-rh-lateralorbitofrontal"
 [9] "ctx-rh-medialorbitofrontal"  "ctx-rh-rostralmiddlefrontal" "ctx-rh-superiorfrontal"      "ctx-rh-frontalpole"         
striatal_regions <- morphdata$morphdata[[1]] %>% 
  filter(str_detect(name, "Putamen|Accumbens|Caudate")) %>% 
  pull(name)

striatal_regions
[1] "Left-Caudate"         "Left-Putamen"         "Left-Accumbens-area"  "Right-Caudate"        "Right-Putamen"        "Right-Accumbens-area"
hipamg_regions <- morphdata$morphdata[[1]] %>% 
  filter(str_detect(name, "Hippocampus|Amygdala")) %>% 
  pull(name)

hipamg_regions
[1] "Left-Hippocampus"  "Left-Amygdala"     "Right-Hippocampus" "Right-Amygdala"   

Now we combine the TACs and the region sizes side-by-side

selected_tacs <- select(tacs, c(ses:rec, tacdata)) %>% 
  inner_join(select(morphdata, c(ses:rec, morphdata))) %>% 
  group_by(sub, ses) %>% 
  mutate(tacdata = map(tacdata, ~pivot_longer(.x, 
                                              cols = `Left-Cerebral-White-Matter`:`ctx-rh-insula`, 
                                              names_to = "name", values_to = "TAC"))) %>% 
  mutate(tacdata = map2(tacdata, morphdata, ~inner_join(.x, .y, by="name")))
Joining with `by = join_by(ses, sub, task, trc, acq, run, rec)`

Then we perform the volume-weighted averaging itself. I define a function to do it for one region, and another function to do it for multiple regions.

volume_weighted_average_tac <- function(tacdata, regions, regionname) {
  
  tacdata_combined <- tacdata %>% 
    filter(name %in% regions) %>% 
    group_by(frame_start, frame_end) %>% 
    mutate(volume_tot = sum(`volume-mm3`),
           volume_frac = `volume-mm3` / volume_tot,
           TAC_frac = TAC * volume_frac) %>% 
    summarise(!!regionname := sum(TAC_frac), 
              .groups = "keep") %>% 
    ungroup()
  
  return(tacdata_combined)
  
}

volume_weighted_average_tacs <- function(tacdata, regions_list) {
  
  regionnames <- names(regions_list)
  
  out <- map2(regions_list, regionnames, ~volume_weighted_average_tac(tacdata, .x, .y))
  
  out <- purrr::reduce(out, dplyr::inner_join, by = c("frame_start",
                                                      "frame_end"))
  
  return(out)
}

regions_list <- list(
  Frontal = frontal_regions,
  Striatum = striatal_regions,
  HippAmg = hipamg_regions
)


selected_tacs <- selected_tacs %>% 
  group_by(sub, ses) %>% 
  mutate(selected_tacdata = map(tacdata, ~volume_weighted_average_tacs(.x, 
                                                                       regions_list)))
  

Combining the TAC and blood (input) data

Now we combine the blood data and PET data so that we can perform modelling using both of them.

modeldata <- selected_tacs %>% 
  select(ses:rec, tacdata = selected_tacdata) %>% 
  inner_join(bloodstream_data)
Joining with `by = join_by(ses, sub, task, trc, acq, run, rec)`

Fitting TACs

Preparation

Many of these steps will be automated when the PET BIDS Derivatives are finalised. But for now, we’ll do these ourselves.

Correcting Units

The TAC data are in Bq/mL, and the bloodstream data are in kBq/mL. This is because Bq/mL is hard to read, but it is the SI unit. Future functions within kinfitr will automatically convert them.

modeldata <- modeldata %>% 
  mutate(tacdata = map(tacdata, ~.x %>% 
                         mutate(across(.cols = Frontal:HippAmg, 
                                       ~unit_convert(.x, 
                                                     from_units = "Bq", 
                                                     to_units = "kBq")))))

Adding frame midtimes and durations

We only have frame_start and frame_end, but we want the midtimes and durations for modelling. We also want the times in minutes for modelling, so we’ll convert them.

modeldata <- modeldata %>% 
  mutate(tacdata = map(tacdata, ~.x %>% 
                         mutate(frame_start = frame_start / 60,
                                frame_end = frame_end / 60) %>% 
                         mutate(frame_dur = frame_end - frame_start,
                                frame_mid = frame_start + 0.5*frame_dur)))

Adding weights

There are many different weighting functions, many of which are included in kinfitr. I’ll use the default one for now. We usually run the weights on a large global grey matter brain region. For today, we’ll just take a mean TAC of our three regions and use that.

modeldata <- modeldata %>% 
  mutate(tacdata = map(tacdata, ~.x %>% 
                         mutate(meanTAC = rowMeans( .x %>% select(Frontal:HippAmg) )) %>% 
                         mutate(weights = weights_create(t_start = frame_start,
                                                         t_end = frame_end,
                                                         tac = meanTAC))))

Fitting a single TAC

Now we will fit the two-tissue compartment model to a single TAC just to see how it looks.

First we should fi

fit_2tc <- twotcm(
       t_tac = modeldata$tacdata[[1]]$frame_mid, 
       tac = modeldata$tacdata[[1]]$Frontal, 
       input = modeldata$input[[1]], 
       weights = modeldata$tacdata[[1]]$weights, 
       vB = 0.05, multstart_iter = 5)

plot(fit_2tc)


fit_2tc$par

That worked!

Notice that in the parameters, we have estimated an inpshift. This is the delay: a shift in the timing of the blood (input) data to the same time as the PET. This is necessary both because the tracer arrives at the brain at a slightly different time from when it arrives at the arm where we measure it, but also because it is hard for these two clocks to be perfectly synchronised.

We would usually fit the delay using the first 5 or 10 minutes of the acquisition using a large region for each individual with a one-tissue compartment model and check that. Then we would use that value when fitting the later models. That information is covered more fully in the full on my website.

Fitting multiple TACs

Here we’ll use a linearised model because they fit more quickly, in this case Logan. They are also less sensitive to the delay time, so we can probably reasonably safely ignore it. But most linearised models require a t* time to operate.

Selecting a t* time

Let’s choose an appropriate t* value

modeldata %>% 
  ungroup() %>% 
  filter(ses=="baseline") %>% 
  slice(1:5) %>% 
  mutate(tstarplot = map2(tacdata, input, 
     ~Logan_tstar(
         t_tac = .x$frame_mid, 
         lowroi =  .x$HippAmg,
         medroi =  .x$Frontal,
         highroi = .x$Striatum, 
         input = .y,
         vB = 0.05)
     )) %>% 
  pull(tstarplot)
Warning: There were 15 warnings in `mutate()`.
The first warning was:
ℹ In argument: `tstarplot = map2(...)`.
Caused by warning:
! Removed 1 row containing missing values or values outside the scale range (`geom_point()`).
ℹ Run ]8;;ide:run:dplyr::last_dplyr_warnings()dplyr::last_dplyr_warnings()]8;; to see the 14 remaining warnings.
[[1]]

[[2]]

[[3]]

[[4]]

[[5]]

Ok, let’s use 13 frames, corresponding with 57.5 minutes.

Fitting

Let’s focus on the frontal cortex, and run the fitting for all measurements.

modeldata <- modeldata %>% 
  group_by(sub, ses) %>% 
  mutate(Logan_fit = map2(tacdata, input, ~Loganplot(t_tac = .x$frame_mid,
                                             tac = .x$Frontal, 
                                             input= .y, 
                                             tstarIncludedFrames = 13, 
                                             weights = .x$weights, 
                                             vB = 0.05, 
                                             dur = .x$frame_dur)))

Plotting

Let’s see a few fits

map(modeldata[1:6,]$Logan_fit, plot)
[[1]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_point()`).

[[2]]
Warning: Removed 2 rows containing missing values or values outside the scale range (`geom_point()`).

[[3]]
Warning: Removed 2 rows containing missing values or values outside the scale range (`geom_point()`).

[[4]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_point()`).

[[5]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_point()`).

[[6]]
Warning: Removed 1 row containing missing values or values outside the scale range (`geom_point()`).

Outcomes

Now let’s observe the resulting VT values.

Logan_outcomes <- modeldata %>% 
  select(sub, ses, Logan_fit) %>% 
  mutate(Vt = map_dbl(Logan_fit, c("par", "Vt"))) %>% 
  select(-Logan_fit)

ggplot(Logan_outcomes, aes(x=ses, y=Vt, colour=sub, group=sub)) +
  geom_point(size=3, colour="black") +
  geom_point(size=2.5) +
  geom_line() + 
  scale_y_log10()

As we can see, they are reduced after blocking with calecoxib as expected.

LS0tCnRpdGxlOiAiT0hCTTIwMjQgVHV0b3JpYWw6IFRBQyBEYXRhIEhhbmRzLW9uIgpvdXRwdXQ6IGh0bWxfbm90ZWJvb2sKLS0tCgojIFNldHVwCgojIyBQYWNrYWdlcwoKRmlyc3Qgd2UgbG9hZCB1cCB0aGUgcGFja2FnZXMgd2UnbGwgYmUgd29ya2luZyB3aXRoCgpgYGB7cn0KIyByZW1vdGVzOjppbnN0YWxsX2dpdGh1YigibWF0aGVzb25nL2Jsb29kc3RyZWFtIikKIyByZW1vdGVzOjppbnN0YWxsX2dpdGh1YigibWF0aGVzb25nL2tpbmZpdHIiKQoKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoYmxvb2RzdHJlYW0pCmxpYnJhcnkoa2luZml0cikKbGlicmFyeShqb2IpCgp0aGVtZV9zZXQodGhlbWVfbGlnaHQoKSkKYGBgCgoKIyMgRGF0YQoKSGVyZSBpcyBhbiBvcHRpb25hbCBjaHVuayB0byBkb3dubG9hZCB0aGUgbWluaWZpZWQgZGF0YXNldC4gSG93ZXZlciwgaXQgc2hvdWxkIGFscmVhZHkgYmUgaW5jbHVkZWQgaW4gdGhlIGJpbmRlci4KCmBgYHtyLCBldmFsPUZBTFNFfQpkb3dubG9hZC5maWxlKCJodHRwczovL3d3dy5kcm9wYm94LmNvbS9zY2wvZmkvYXgxdDNsZm9wM3BwM2g0Y2lyMG03L2RzMDA0ODY5LnppcD9ybGtleT02ejdlOXdvcHVkOW5ndTI2ZWtiZDNrZmtzJmRsPTEiLCAKICAgICAgICAgICAgICBkZXN0ZmlsZSA9ICJkczAwNDg2OS56aXAiKQpgYGAKCmBgYHtyfQp1bnppcCh6aXBmaWxlID0gImRzMDA0ODY5LnppcCIpCmBgYAoKCgojIEJsb29kIEFuYWx5c2lzCgpIZXJlIHdlIGNhbiBydW4gYSBiYXNpYyBibG9vZCBhbmFseXNpcy4gIEZvciBhbnlvbmUgd2hvIHdhbnRzIHRvIHJ1biBpdCwgSSd2ZSBwbGFjZWQgaXQgaW4gYSBqb2IgY2FsbCwgd2hpY2ggcnVucyBpdCBpbiB0aGUgYmFja2dyb3VuZCwgYnV0IGl0IG1heSBvciBtYXkgbm90IG1ha2UgeW91ciBzZXNzaW9uIHNsb3cuCgpgYGB7ciwgZXZhbD1GQUxTRX0Kam9iKHsgCiAgYmxvb2RzdHJlYW0oImRzMDA0ODY5LyIpIAp9KQpgYGAKCkFsdGVybmF0aXZlbHksIGlmIHlvdSB3YW50IHRvIHVzZSBhIGNvbmZpZ3VyYXRpb24gZmlsZSwgdGhpcyBpcyBob3cgeW91IGNvdWxkIGRvIHNvLiBZb3UgY2FuIGluc3BlY3QgdGhlIGNvbmZpZyBmaWxlIGluIHRoZSByZWxldmFudCBkaXJlY3RvcnkuIEJ1dCBiZXN0IG5vdCB0byBydW4gdGhpczogdGhpcyB0YWtlcyBhIGxpdHRsZSBsb25nZXIsIGFuZCBpdCBpcyBub3QgaW4gYSBqb2IgY2FsbCBlaXRoZXIsIHNvIHlvdSB3b24ndCBiZSBhYmxlIHRvIGRvIGFueXRoaW5nIHVudGlsIGl0IGlzIGNvbXBsZXRlZC4KCmBgYHtyLCBldmFsPUZBTFNFfQpibG9vZHN0cmVhbSgiZHMwMDQ4NjkvIiwgY29uZmlncGF0aCA9ICJkczAwNDg2OS9jb2RlL2NvbmZpZ190dXRvcmlhbC5qc29uIikgCmBgYAoKCgojIExvYWRpbmcgRGF0YQoKIyMgTG9hZGluZyBibG9vZHN0cmVhbSBvdXRwdXRzCgpXZSB3YW50IHRoZSBhcnRlcmlhbCBpbnB1dCBmdW5jdGlvbiBkYXRhLCB3aGljaCBhcmUgY2FsbGVkICJpbnB1dCIuCgoKYGBge3J9CmJsb29kc3RyZWFtX2RhdGEgPC0gYmxvb2RzdHJlYW1faW1wb3J0X2lucHV0ZnVuY3Rpb25zKCJkczAwNDg2OS9kZXJpdmF0aXZlcy9ibG9vZHN0cmVhbXR1dG9yaWFsLyIpICU+JQogIHNlbGVjdCgtbWVhc3VyZW1lbnQpCmBgYAoKTGV0J3MgcGxvdCBhIGZldyBvZiB0aGVtIHRvIHNlZS4gIFRoZXNlIGNvbnRhaW4gdGhlIGludGVycG9sYXRlZCBjdXJ2ZXMsIGFuZCBjYW4gYmUgdXNlZCB3aXRoIGBraW5maXRyYCB0byBwZXJmb3JtIGtpbmV0aWMgbW9kZWxsaW5nLgoKYGBge3J9CmhlYWQoYmxvb2RzdHJlYW1fZGF0YSwgbiA9IDYpCgpibG9vZHN0cmVhbV9kYXRhICU+JSAKICBzbGljZSgxOjYpICU+JSAKICBwdWxsKGlucHV0KSAlPiUgCiAgbWFwKHBsb3QpCmBgYAoKCgojIyBMb2FkaW5nIHBldHByZXAgb3V0cHV0cyAoVEFDcyBhbmQgbW9ycGhvbG9neSkKCk5vdyB3ZSB3YW50IHRvIGxvYWQgdXAgdGhlIGBwZXRwcmVwYCBvdXRwdXRzLiBUaGVyZSB3aWxsIGV2ZW50dWFsbHkgYmUgYSBmdW5jdGlvbiBmb3IgYXV0b21hdGluZyB0aGUgbmV4dCBmZXcgc3RlcHMsIGJ1dCB0aGUgUEVUIEJJRFMgRGVyaXZhdGl2ZXMgZ3VpZGVsaW5lcyBhcmUgbm90IHlldCBuYWlsZWQgZG93biwgc28gd2UnbGwgZG8gaXQgbWFudWFsbHkgZm9yIG5vdy4KCkZpcnN0IHdlIHBhcnNlIHRoZSBmaWxlcyBhbmQgbG9hZCBpbiB0aGUgZGF0YSBmb3IgdGhlIHRhY3MgYW5kIHRoZSBtb3JwaG9sb2d5LgoKCmBgYHtyfQpwZXRwcmVwX2RhdGEgPC0ga2luZml0cjo6Ymlkc19wYXJzZV9maWxlcygiZHMwMDQ4NjkvZGVyaXZhdGl2ZXMvcGV0cHJlcF9leHRyYWN0X3RhY3MvIikgJT4lIAogIHVubmVzdChmaWxlZGF0YSkgJT4lIAogIGZpbHRlcihzdHJfZGV0ZWN0KHBhdGhfYWJzb2x1dGUsICJndG1zZWciKSkKCnRhY3MgPC0gcGV0cHJlcF9kYXRhICU+JSAKICBmaWx0ZXIobWVhc3VyZW1lbnQ9PSJ0YWNzIikgJT4lIAogIGZpbHRlcihpcy5uYShwdmMpKSAlPiUgCiAgbXV0YXRlKHRhY2RhdGEgPSBtYXAocGF0aF9hYnNvbHV0ZSwgfnJlYWRfZGVsaW0oLngsIGRlbGltPSJcdCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfY29sX3R5cGVzID0gRkFMU0UpKSkKCm1vcnBoZGF0YSA8LSBwZXRwcmVwX2RhdGEgJT4lCiAgZmlsdGVyKG1lYXN1cmVtZW50PT0ibW9ycGgiKSAlPiUKICBtdXRhdGUobW9ycGhkYXRhID0gbWFwKHBhdGhfYWJzb2x1dGUsIH5yZWFkX2RlbGltKC54LCBkZWxpbT0iXHQiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNob3dfY29sX3R5cGVzID0gRkFMU0UpKSkKYGBgCgoKTm93IHdlIGhhdmUgbG90cyBvZiBsaXR0bGUgcmVnaW9ucyBvZiBpbnRlcmVzdCwgYnV0IHRoZXkgYXJlIGFsbCB2ZXJ5IHNtYWxsLiBUeXBpY2FsbHksIHdlIHdvdWxkIHdhbnQgdG8gY29tYmluZSByZWdpb25zIGludG8gbGFyZ2VyIHJlZ2lvbnMgdGhhdCB3ZSBhcmUgaW50ZXJlc3RlZCBpbi4gV2UgZG8gc28gdXNpbmcgdm9sdW1lLXdlaWdodGVkIGF2ZXJhZ2luZywgd2hpY2ggcmVxdWlyZXMgYm90aCB0aGUgbW9ycGhvbG9neSBhbmQgdGhlIFRBQyBpbmZvcm1hdGlvbi4gVGhpcyB3aWxsIGFsc28gc29vbiBiZSBhdXRvbWF0ZWQgb25jZSB0aGUgUEVUIEJJRFMgRGVyaXZhdGl2ZXMgYXJlIG1vcmUgZmluYWxpc2VkLgoKIyMgQ3JlYXRpbmcgY29tYmluZWQgVEFDcwoKV2UnbGwgY3JlYXRlIGEgZnJvbnRhbCBjb3J0ZXggcmVnaW9uLCBhIHN0cmlhdGFsIHJlZ2lvbiBhbmQgYSBoaXBwb2NhbXB1cy1hbXlnZGFsYSByZWdpb24uCgpGaXJzdCwgbGV0J3MgZGVmaW5lIHRoZSByZWdpb25zIGJ5IHRoZWlyIGNvbnN0aXR1dGVudCBwYXJ0cy4KCmBgYHtyfQpmcm9udGFsX3JlZ2lvbnMgPC0gbW9ycGhkYXRhJG1vcnBoZGF0YVtbMV1dICU+JSAKICBmaWx0ZXIoc3RyX2RldGVjdChuYW1lLCAiZnJvbnRhbCIpKSAlPiUgCiAgcHVsbChuYW1lKQoKZnJvbnRhbF9yZWdpb25zCmBgYAoKCmBgYHtyfQpzdHJpYXRhbF9yZWdpb25zIDwtIG1vcnBoZGF0YSRtb3JwaGRhdGFbWzFdXSAlPiUgCiAgZmlsdGVyKHN0cl9kZXRlY3QobmFtZSwgIlB1dGFtZW58QWNjdW1iZW5zfENhdWRhdGUiKSkgJT4lIAogIHB1bGwobmFtZSkKCnN0cmlhdGFsX3JlZ2lvbnMKYGBgCgoKYGBge3J9CmhpcGFtZ19yZWdpb25zIDwtIG1vcnBoZGF0YSRtb3JwaGRhdGFbWzFdXSAlPiUgCiAgZmlsdGVyKHN0cl9kZXRlY3QobmFtZSwgIkhpcHBvY2FtcHVzfEFteWdkYWxhIikpICU+JSAKICBwdWxsKG5hbWUpCgpoaXBhbWdfcmVnaW9ucwpgYGAKCgpOb3cgd2UgY29tYmluZSB0aGUgVEFDcyBhbmQgdGhlIHJlZ2lvbiBzaXplcyBzaWRlLWJ5LXNpZGUKCmBgYHtyfQpzZWxlY3RlZF90YWNzIDwtIHNlbGVjdCh0YWNzLCBjKHNlczpyZWMsIHRhY2RhdGEpKSAlPiUgCiAgaW5uZXJfam9pbihzZWxlY3QobW9ycGhkYXRhLCBjKHNlczpyZWMsIG1vcnBoZGF0YSkpKSAlPiUgCiAgZ3JvdXBfYnkoc3ViLCBzZXMpICU+JSAKICBtdXRhdGUodGFjZGF0YSA9IG1hcCh0YWNkYXRhLCB+cGl2b3RfbG9uZ2VyKC54LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbHMgPSBgTGVmdC1DZXJlYnJhbC1XaGl0ZS1NYXR0ZXJgOmBjdHgtcmgtaW5zdWxhYCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuYW1lc190byA9ICJuYW1lIiwgdmFsdWVzX3RvID0gIlRBQyIpKSkgJT4lIAogIG11dGF0ZSh0YWNkYXRhID0gbWFwMih0YWNkYXRhLCBtb3JwaGRhdGEsIH5pbm5lcl9qb2luKC54LCAueSwgYnk9Im5hbWUiKSkpCmBgYAoKVGhlbiB3ZSBwZXJmb3JtIHRoZSB2b2x1bWUtd2VpZ2h0ZWQgYXZlcmFnaW5nIGl0c2VsZi4gSSBkZWZpbmUgYSBmdW5jdGlvbiB0byBkbyBpdCBmb3Igb25lIHJlZ2lvbiwgYW5kIGFub3RoZXIgZnVuY3Rpb24gdG8gZG8gaXQgZm9yIG11bHRpcGxlIHJlZ2lvbnMuCgpgYGB7cn0Kdm9sdW1lX3dlaWdodGVkX2F2ZXJhZ2VfdGFjIDwtIGZ1bmN0aW9uKHRhY2RhdGEsIHJlZ2lvbnMsIHJlZ2lvbm5hbWUpIHsKICAKICB0YWNkYXRhX2NvbWJpbmVkIDwtIHRhY2RhdGEgJT4lIAogICAgZmlsdGVyKG5hbWUgJWluJSByZWdpb25zKSAlPiUgCiAgICBncm91cF9ieShmcmFtZV9zdGFydCwgZnJhbWVfZW5kKSAlPiUgCiAgICBtdXRhdGUodm9sdW1lX3RvdCA9IHN1bShgdm9sdW1lLW1tM2ApLAogICAgICAgICAgIHZvbHVtZV9mcmFjID0gYHZvbHVtZS1tbTNgIC8gdm9sdW1lX3RvdCwKICAgICAgICAgICBUQUNfZnJhYyA9IFRBQyAqIHZvbHVtZV9mcmFjKSAlPiUgCiAgICBzdW1tYXJpc2UoISFyZWdpb25uYW1lIDo9IHN1bShUQUNfZnJhYyksIAogICAgICAgICAgICAgIC5ncm91cHMgPSAia2VlcCIpICU+JSAKICAgIHVuZ3JvdXAoKQogIAogIHJldHVybih0YWNkYXRhX2NvbWJpbmVkKQogIAp9Cgp2b2x1bWVfd2VpZ2h0ZWRfYXZlcmFnZV90YWNzIDwtIGZ1bmN0aW9uKHRhY2RhdGEsIHJlZ2lvbnNfbGlzdCkgewogIAogIHJlZ2lvbm5hbWVzIDwtIG5hbWVzKHJlZ2lvbnNfbGlzdCkKICAKICBvdXQgPC0gbWFwMihyZWdpb25zX2xpc3QsIHJlZ2lvbm5hbWVzLCB+dm9sdW1lX3dlaWdodGVkX2F2ZXJhZ2VfdGFjKHRhY2RhdGEsIC54LCAueSkpCiAgCiAgb3V0IDwtIHB1cnJyOjpyZWR1Y2Uob3V0LCBkcGx5cjo6aW5uZXJfam9pbiwgYnkgPSBjKCJmcmFtZV9zdGFydCIsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICJmcmFtZV9lbmQiKSkKICAKICByZXR1cm4ob3V0KQp9CgpyZWdpb25zX2xpc3QgPC0gbGlzdCgKICBGcm9udGFsID0gZnJvbnRhbF9yZWdpb25zLAogIFN0cmlhdHVtID0gc3RyaWF0YWxfcmVnaW9ucywKICBIaXBwQW1nID0gaGlwYW1nX3JlZ2lvbnMKKQoKCnNlbGVjdGVkX3RhY3MgPC0gc2VsZWN0ZWRfdGFjcyAlPiUgCiAgZ3JvdXBfYnkoc3ViLCBzZXMpICU+JSAKICBtdXRhdGUoc2VsZWN0ZWRfdGFjZGF0YSA9IG1hcCh0YWNkYXRhLCB+dm9sdW1lX3dlaWdodGVkX2F2ZXJhZ2VfdGFjcygueCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgcmVnaW9uc19saXN0KSkpCiAgCmBgYAoKCgoKIyBDb21iaW5pbmcgdGhlIFRBQyBhbmQgYmxvb2QgKGlucHV0KSBkYXRhCgpOb3cgd2UgY29tYmluZSB0aGUgYmxvb2QgZGF0YSBhbmQgUEVUIGRhdGEgc28gdGhhdCB3ZSBjYW4gcGVyZm9ybSBtb2RlbGxpbmcgdXNpbmcgYm90aCBvZiB0aGVtLgoKYGBge3J9Cm1vZGVsZGF0YSA8LSBzZWxlY3RlZF90YWNzICU+JSAKICBzZWxlY3Qoc2VzOnJlYywgdGFjZGF0YSA9IHNlbGVjdGVkX3RhY2RhdGEpICU+JSAKICBpbm5lcl9qb2luKGJsb29kc3RyZWFtX2RhdGEpCmBgYAoKCiMgRml0dGluZyBUQUNzCgojIyBQcmVwYXJhdGlvbgoKTWFueSBvZiB0aGVzZSBzdGVwcyB3aWxsIGJlIGF1dG9tYXRlZCB3aGVuIHRoZSBQRVQgQklEUyBEZXJpdmF0aXZlcyBhcmUgZmluYWxpc2VkLiBCdXQgZm9yIG5vdywgd2UnbGwgZG8gdGhlc2Ugb3Vyc2VsdmVzLgoKIyMjIENvcnJlY3RpbmcgVW5pdHMKClRoZSBUQUMgZGF0YSBhcmUgaW4gQnEvbUwsIGFuZCB0aGUgYmxvb2RzdHJlYW0gZGF0YSBhcmUgaW4ga0JxL21MLiBUaGlzIGlzIGJlY2F1c2UgQnEvbUwgaXMgaGFyZCB0byByZWFkLCBidXQgaXQgaXMgdGhlIFNJIHVuaXQuIEZ1dHVyZSBmdW5jdGlvbnMgd2l0aGluIGBraW5maXRyYCB3aWxsIGF1dG9tYXRpY2FsbHkgY29udmVydCB0aGVtLgoKYGBge3J9Cm1vZGVsZGF0YSA8LSBtb2RlbGRhdGEgJT4lIAogIG11dGF0ZSh0YWNkYXRhID0gbWFwKHRhY2RhdGEsIH4ueCAlPiUgCiAgICAgICAgICAgICAgICAgICAgICAgICBtdXRhdGUoYWNyb3NzKC5jb2xzID0gRnJvbnRhbDpIaXBwQW1nLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgfnVuaXRfY29udmVydCgueCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJvbV91bml0cyA9ICJCcSIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRvX3VuaXRzID0gImtCcSIpKSkpKQpgYGAKCiMjIyBBZGRpbmcgZnJhbWUgbWlkdGltZXMgYW5kIGR1cmF0aW9ucwoKV2Ugb25seSBoYXZlIGZyYW1lX3N0YXJ0IGFuZCBmcmFtZV9lbmQsIGJ1dCB3ZSB3YW50IHRoZSBtaWR0aW1lcyBhbmQgZHVyYXRpb25zIGZvciBtb2RlbGxpbmcuIFdlIGFsc28gd2FudCB0aGUgdGltZXMgaW4gbWludXRlcyBmb3IgbW9kZWxsaW5nLCBzbyB3ZSdsbCBjb252ZXJ0IHRoZW0uCgpgYGB7cn0KbW9kZWxkYXRhIDwtIG1vZGVsZGF0YSAlPiUgCiAgbXV0YXRlKHRhY2RhdGEgPSBtYXAodGFjZGF0YSwgfi54ICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShmcmFtZV9zdGFydCA9IGZyYW1lX3N0YXJ0IC8gNjAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZnJhbWVfZW5kID0gZnJhbWVfZW5kIC8gNjApICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZShmcmFtZV9kdXIgPSBmcmFtZV9lbmQgLSBmcmFtZV9zdGFydCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBmcmFtZV9taWQgPSBmcmFtZV9zdGFydCArIDAuNSpmcmFtZV9kdXIpKSkKYGBgCgoKIyMjIEFkZGluZyB3ZWlnaHRzCgpUaGVyZSBhcmUgbWFueSBkaWZmZXJlbnQgd2VpZ2h0aW5nIGZ1bmN0aW9ucywgbWFueSBvZiB3aGljaCBhcmUgaW5jbHVkZWQgaW4gYGtpbmZpdHJgLiBJJ2xsIHVzZSB0aGUgZGVmYXVsdCBvbmUgZm9yIG5vdy4gV2UgdXN1YWxseSBydW4gdGhlIHdlaWdodHMgb24gYSBsYXJnZSBnbG9iYWwgZ3JleSBtYXR0ZXIgYnJhaW4gcmVnaW9uLiBGb3IgdG9kYXksIHdlJ2xsIGp1c3QgdGFrZSBhIG1lYW4gVEFDIG9mIG91ciB0aHJlZSByZWdpb25zIGFuZCB1c2UgdGhhdC4KCmBgYHtyfQptb2RlbGRhdGEgPC0gbW9kZWxkYXRhICU+JSAKICBtdXRhdGUodGFjZGF0YSA9IG1hcCh0YWNkYXRhLCB+LnggJT4lIAogICAgICAgICAgICAgICAgICAgICAgICAgbXV0YXRlKG1lYW5UQUMgPSByb3dNZWFucyggLnggJT4lIHNlbGVjdChGcm9udGFsOkhpcHBBbWcpICkpICU+JSAKICAgICAgICAgICAgICAgICAgICAgICAgIG11dGF0ZSh3ZWlnaHRzID0gd2VpZ2h0c19jcmVhdGUodF9zdGFydCA9IGZyYW1lX3N0YXJ0LAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0X2VuZCA9IGZyYW1lX2VuZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFjID0gbWVhblRBQykpKSkKYGBgCgoKIyMgRml0dGluZyBhIHNpbmdsZSBUQUMKCk5vdyB3ZSB3aWxsIGZpdCB0aGUgdHdvLXRpc3N1ZSBjb21wYXJ0bWVudCBtb2RlbCB0byBhIHNpbmdsZSBUQUMganVzdCB0byBzZWUgaG93IGl0IGxvb2tzLgoKRmlyc3Qgd2Ugc2hvdWxkIGZpCgpgYGB7cn0KZml0XzJ0YyA8LSB0d290Y20oCiAgICAgICB0X3RhYyA9IG1vZGVsZGF0YSR0YWNkYXRhW1sxXV0kZnJhbWVfbWlkLCAKICAgICAgIHRhYyA9IG1vZGVsZGF0YSR0YWNkYXRhW1sxXV0kRnJvbnRhbCwgCiAgICAgICBpbnB1dCA9IG1vZGVsZGF0YSRpbnB1dFtbMV1dLCAKICAgICAgIHdlaWdodHMgPSBtb2RlbGRhdGEkdGFjZGF0YVtbMV1dJHdlaWdodHMsIAogICAgICAgdkIgPSAwLjA1LCBtdWx0c3RhcnRfaXRlciA9IDUpCgpwbG90KGZpdF8ydGMpCgpmaXRfMnRjJHBhcgpgYGAKClRoYXQgd29ya2VkISAKCk5vdGljZSB0aGF0IGluIHRoZSBwYXJhbWV0ZXJzLCB3ZSBoYXZlIGVzdGltYXRlZCBhbiBgaW5wc2hpZnRgLiBUaGlzIGlzIHRoZSBkZWxheTogYSBzaGlmdCBpbiB0aGUgdGltaW5nIG9mIHRoZSBibG9vZCAoaW5wdXQpIGRhdGEgdG8gdGhlIHNhbWUgdGltZSBhcyB0aGUgUEVULiBUaGlzIGlzIG5lY2Vzc2FyeSBib3RoIGJlY2F1c2UgdGhlIHRyYWNlciBhcnJpdmVzIGF0IHRoZSBicmFpbiBhdCBhIHNsaWdodGx5IGRpZmZlcmVudCB0aW1lIGZyb20gd2hlbiBpdCBhcnJpdmVzIGF0IHRoZSBhcm0gd2hlcmUgd2UgbWVhc3VyZSBpdCwgYnV0IGFsc28gYmVjYXVzZSBpdCBpcyBoYXJkIGZvciB0aGVzZSB0d28gY2xvY2tzIHRvIGJlIHBlcmZlY3RseSBzeW5jaHJvbmlzZWQuIAoKV2Ugd291bGQgdXN1YWxseSBmaXQgdGhlIGRlbGF5IHVzaW5nIHRoZSBmaXJzdCA1IG9yIDEwIG1pbnV0ZXMgb2YgdGhlIGFjcXVpc2l0aW9uIHVzaW5nIGEgbGFyZ2UgcmVnaW9uIGZvciBlYWNoIGluZGl2aWR1YWwgd2l0aCBhIG9uZS10aXNzdWUgY29tcGFydG1lbnQgbW9kZWwgYW5kIGNoZWNrIHRoYXQuIFRoZW4gd2Ugd291bGQgdXNlIHRoYXQgdmFsdWUgd2hlbiBmaXR0aW5nIHRoZSBsYXRlciBtb2RlbHMuIFRoYXQgaW5mb3JtYXRpb24gaXMgY292ZXJlZCBtb3JlIGZ1bGx5IGluIHRoZSBmdWxsIG9uIG15IHdlYnNpdGUuCgoKIyMgRml0dGluZyBtdWx0aXBsZSBUQUNzCgpIZXJlIHdlJ2xsIHVzZSBhIGxpbmVhcmlzZWQgbW9kZWwgYmVjYXVzZSB0aGV5IGZpdCBtb3JlIHF1aWNrbHksIGluIHRoaXMgY2FzZSBMb2dhbi4gVGhleSBhcmUgYWxzbyBsZXNzIHNlbnNpdGl2ZSB0byB0aGUgZGVsYXkgdGltZSwgc28gd2UgY2FuIHByb2JhYmx5IHJlYXNvbmFibHkgc2FmZWx5IGlnbm9yZSBpdC4gIEJ1dCBtb3N0IGxpbmVhcmlzZWQgbW9kZWxzIHJlcXVpcmUgYSB0XCogdGltZSB0byBvcGVyYXRlLgoKIyMjIFNlbGVjdGluZyBhIHRcKiB0aW1lCgpMZXQncyBjaG9vc2UgYW4gYXBwcm9wcmlhdGUgdFwqIHZhbHVlCgpgYGB7ciwgZmlnLndpZHRoPTEyLCBmaWcuaGVpZ2h0PTEyfQptb2RlbGRhdGEgJT4lIAogIHVuZ3JvdXAoKSAlPiUgCiAgZmlsdGVyKHNlcz09ImJhc2VsaW5lIikgJT4lIAogIHNsaWNlKDE6NSkgJT4lIAogIG11dGF0ZSh0c3RhcnBsb3QgPSBtYXAyKHRhY2RhdGEsIGlucHV0LCAKICAgICB+TG9nYW5fdHN0YXIoCiAgICAgICAgIHRfdGFjID0gLngkZnJhbWVfbWlkLCAKICAgICAgICAgbG93cm9pID0gIC54JEhpcHBBbWcsCiAgICAgICAgIG1lZHJvaSA9ICAueCRGcm9udGFsLAogICAgICAgICBoaWdocm9pID0gLngkU3RyaWF0dW0sIAogICAgICAgICBpbnB1dCA9IC55LAogICAgICAgICB2QiA9IDAuMDUpCiAgICAgKSkgJT4lIAogIHB1bGwodHN0YXJwbG90KQpgYGAKCk9rLCBsZXQncyB1c2UgMTMgZnJhbWVzLCBjb3JyZXNwb25kaW5nIHdpdGggNTcuNSBtaW51dGVzLiAKCiMjIyBGaXR0aW5nCgpMZXQncyBmb2N1cyBvbiB0aGUgZnJvbnRhbCBjb3J0ZXgsIGFuZCBydW4gdGhlIGZpdHRpbmcgZm9yIGFsbCBtZWFzdXJlbWVudHMuCgpgYGB7cn0KbW9kZWxkYXRhIDwtIG1vZGVsZGF0YSAlPiUgCiAgZ3JvdXBfYnkoc3ViLCBzZXMpICU+JSAKICBtdXRhdGUoTG9nYW5fZml0ID0gbWFwMih0YWNkYXRhLCBpbnB1dCwgfkxvZ2FucGxvdCh0X3RhYyA9IC54JGZyYW1lX21pZCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGFjID0gLngkRnJvbnRhbCwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlucHV0PSAueSwgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRzdGFySW5jbHVkZWRGcmFtZXMgPSAxMywgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHdlaWdodHMgPSAueCR3ZWlnaHRzLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdkIgPSAwLjA1LCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgZHVyID0gLngkZnJhbWVfZHVyKSkpCmBgYAoKCiMjIyBQbG90dGluZwoKTGV0J3Mgc2VlIGEgZmV3IGZpdHMKCmBgYHtyfQptYXAobW9kZWxkYXRhWzE6NixdJExvZ2FuX2ZpdCwgcGxvdCkKYGBgCgoKCiMjIyBPdXRjb21lcwoKTm93IGxldCdzIG9ic2VydmUgdGhlIHJlc3VsdGluZyBWflR+IHZhbHVlcy4KCmBgYHtyfQpMb2dhbl9vdXRjb21lcyA8LSBtb2RlbGRhdGEgJT4lIAogIHNlbGVjdChzdWIsIHNlcywgTG9nYW5fZml0KSAlPiUgCiAgbXV0YXRlKFZ0ID0gbWFwX2RibChMb2dhbl9maXQsIGMoInBhciIsICJWdCIpKSkgJT4lIAogIHNlbGVjdCgtTG9nYW5fZml0KQoKZ2dwbG90KExvZ2FuX291dGNvbWVzLCBhZXMoeD1zZXMsIHk9VnQsIGNvbG91cj1zdWIsIGdyb3VwPXN1YikpICsKICBnZW9tX3BvaW50KHNpemU9MywgY29sb3VyPSJibGFjayIpICsKICBnZW9tX3BvaW50KHNpemU9Mi41KSArCiAgZ2VvbV9saW5lKCkgKyAKICBzY2FsZV95X2xvZzEwKCkKYGBgCgpBcyB3ZSBjYW4gc2VlLCB0aGV5IGFyZSByZWR1Y2VkIGFmdGVyIGJsb2NraW5nIHdpdGggY2FsZWNveGliIGFzIGV4cGVjdGVkLgo=